#include <iostream>
#include <fstream>

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>

#include "processing.h"
#include "../cnn/cnn.h"
#include "../tracking/tracking.h"

using namespace std;

#define GREEN cv::Scalar(50, 180, 20)
#define WHITE cv::Scalar(255, 255, 255)

extern cv::Mat frame;
extern vector<cv::Rect> bboxes;
extern std::vector<Droplet> droplets;
extern bool status;

const int BOARD_WIDTH = 900;
const int BOARD_HEIGHT = 550;
cv::Mat transformMatrix;
cv::Size BOARD_SIZE(BOARD_WIDTH, BOARD_HEIGHT);
cv::Point2f srcAnchors[4];
cv::Point2f dstCorners[4] = {
        cv::Point(20, 15),
        cv::Point(880, 15),
        cv::Point(880, 535),
        cv::Point(20, 535)
};

int labelMatrix[BOARD_HEIGHT][BOARD_WIDTH];

std::vector<uchar>buf;

bool drawCross(uint x, uint y, uint radius, uint thickness) {
    try {
        int x1 = x - radius, y1 = y;
        int x2 = x + radius, y2 = y;
        cv::line(frame, cv::Point(x1, y1), cv::Point(x2, y2), GREEN, thickness, 8, 0);
        x1 = x, y1 = y - radius;
        x2 = x, y2 = y + radius;
        cv::line(frame, cv::Point(x1, y1), cv::Point(x2, y2), GREEN, thickness, 8, 0);
        return true;
    }
    catch (...) {
        return false;
    }
}

inline int getLabel(int x, int y) {
    return labelMatrix[y][x];
}

inline int getCenterLabel(cv::Rect& box) {
    int x = box.x + box.width / 2;
    int y = box.y + box.height / 2;
    return labelMatrix[y][x];
}

bool readLabelMatrix(char* path) {
    try {
        ifstream file(path);
        if (!file.is_open()) throw string("labelMatrix is not open!");
        for (int i = 0; i < BOARD_HEIGHT; ++i) {
            for (int j = 0; j < BOARD_WIDTH; ++j) {
                file >> labelMatrix[i][j];
            }
        }
        file.close();
        return true;
    }
    catch (...) {
        return false;
    }
}

bool detectDroplets(uint& dropletNum) {
    bool ret = detect();
    if (!ret) return false;
    dropletNum = bboxes.size();
    for (int i = 0; i < bboxes.size(); i++) {
        auto& bbox = bboxes[i];
        cv::Rect textBox{ bbox.x - 2,bbox.y - 25,bbox.width + 4,25 };
        cv::rectangle(frame, textBox, GREEN, -1);
        cv::rectangle(frame, bbox, GREEN, 2, 8, 0);
        cv::putText(frame, "droplet", cv::Point(bbox.x, bbox.y - 2), 0, 0.6, WHITE, 2);
        printf("center label: %02d\n", getCenterLabel(bbox));
    }
    return true;
}

bool dropletCompare(Droplet& A, Droplet& B) {
    return A.id < B.id;
}

bool trackDroplets(uint& dropletNum, uint* results) {
    status = updateTracker();
    if (!status) return false;
    dropletNum = droplets.size();
    unsigned int* p = results;
    std::sort(droplets.begin(), droplets.end(), dropletCompare);
    for (auto& droplet : droplets) {
        auto& id = droplet.id;
        auto& bbox = droplet.bbox;
        cv::Rect tbox{ bbox.x - 2,bbox.y - 25,bbox.width + 4,25 };
        cv::rectangle(frame, tbox, GREEN, -1);
        cv::rectangle(frame, bbox, GREEN, 2, 8, 0);
        cv::putText(frame, to_string(id), cv::Point(bbox.x, bbox.y - 2), 0, 0.8, WHITE, 2);
        // encoding
        *p++ = id;
        *p++ = getCenterLabel(bbox);
    }
    *p = 0xff;
    return true;
}

bool getResultImage(uchar* data, unsigned long& size) {
    try {
        cv::imencode(".bmp", frame, buf);
        size = buf.size();
        for (auto x : buf) {
            *data++ = x;
        }
        return true;
    }
    catch (...) {
        return false;
    }
}

bool getTransformMatrix(uint* anchors) {
    try {
        for (int i = 0; i < 4; ++i) {
            srcAnchors[i].x = anchors[2 * i];
            srcAnchors[i].y = anchors[2 * i + 1];
        }
        transformMatrix = cv::getPerspectiveTransform(srcAnchors, dstCorners);
        return true;
    }
    catch (...) {
        return false;
    }
}

void perspectiveTransform() {
    cv::warpPerspective(frame, frame, transformMatrix, BOARD_SIZE);
}
